/*
 * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.beans.beancontext;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

import java.util.TooManyListenersException;

import java.util.Locale;

/**
 * <p>
 * This helper class provides a utility implementation of the
 * java.beans.beancontext.BeanContextServices interface.
 * </p>
 * <p>
 * Since this class directly implements the BeanContextServices interface,
 * the class can, and is intended to be used either by subclassing this
 * implementation, or via delegation of an instance of this class
 * from another through the BeanContextProxy interface.
 * </p>
 *
 * @author Laurence P. G. Cable
 * @since 1.2
 */

public class BeanContextServicesSupport extends BeanContextSupport
    implements BeanContextServices {

  private static final long serialVersionUID = -8494482757288719206L;

  /**
   * <p>
   * Construct a BeanContextServicesSupport instance
   * </p>
   *
   * @param peer The peer BeanContext we are supplying an implementation for, if null the this
   * object is its own peer
   * @param lcle The current Locale for this BeanContext.
   * @param dTime The initial state, true if in design mode, false if runtime.
   * @param visible The initial visibility.
   */

  public BeanContextServicesSupport(BeanContextServices peer, Locale lcle, boolean dTime,
      boolean visible) {
    super(peer, lcle, dTime, visible);
  }

  /**
   * Create an instance using the specified Locale and design mode.
   *
   * @param peer The peer BeanContext we are supplying an implementation for, if null the this
   * object is its own peer
   * @param lcle The current Locale for this BeanContext.
   * @param dtime The initial state, true if in design mode, false if runtime.
   */

  public BeanContextServicesSupport(BeanContextServices peer, Locale lcle, boolean dtime) {
    this(peer, lcle, dtime, true);
  }

  /**
   * Create an instance using the specified locale
   *
   * @param peer The peer BeanContext we are supplying an implementation for, if null the this
   * object is its own peer
   * @param lcle The current Locale for this BeanContext.
   */

  public BeanContextServicesSupport(BeanContextServices peer, Locale lcle) {
    this(peer, lcle, false, true);
  }

  /**
   * Create an instance with a peer
   *
   * @param peer The peer BeanContext we are supplying an implementation for, if null the this
   * object is its own peer
   */

  public BeanContextServicesSupport(BeanContextServices peer) {
    this(peer, null, false, true);
  }

  /**
   * Create an instance that is not a delegate of another object
   */

  public BeanContextServicesSupport() {
    this(null, null, false, true);
  }

  /**
   * called by BeanContextSupport superclass during construction and
   * deserialization to initialize subclass transient state.
   *
   * subclasses may envelope this method, but should not override it or
   * call it directly.
   */

  public void initialize() {
    super.initialize();

    services = new HashMap(serializable + 1);
    bcsListeners = new ArrayList(1);
  }

  /**
   * Gets the <tt>BeanContextServices</tt> associated with this
   * <tt>BeanContextServicesSupport</tt>.
   *
   * @return the instance of <tt>BeanContext</tt> this object is providing the implementation for.
   */
  public BeanContextServices getBeanContextServicesPeer() {
    return (BeanContextServices) getBeanContextChildPeer();
  }

  /************************************************************************/

    /*
     * protected nested class containing per child information, an instance
     * of which is associated with each child in the "children" hashtable.
     * subclasses can extend this class to include their own per-child state.
     *
     * Note that this 'value' is serialized with the corresponding child 'key'
     * when the BeanContextSupport is serialized.
     */

  protected class BCSSChild extends BeanContextSupport.BCSChild {

    private static final long serialVersionUID = -3263851306889194873L;

        /*
         * private nested class to map serviceClass to Provider and requestors
         * listeners.
         */

    class BCSSCServiceClassRef {

      // create an instance of a service ref

      BCSSCServiceClassRef(Class sc, BeanContextServiceProvider bcsp, boolean delegated) {
        super();

        serviceClass = sc;

        if (delegated) {
          delegateProvider = bcsp;
        } else {
          serviceProvider = bcsp;
        }
      }

      // add a requestor and assoc listener

      void addRequestor(Object requestor, BeanContextServiceRevokedListener bcsrl)
          throws TooManyListenersException {
        BeanContextServiceRevokedListener cbcsrl = (BeanContextServiceRevokedListener) requestors
            .get(requestor);

        if (cbcsrl != null && !cbcsrl.equals(bcsrl)) {
          throw new TooManyListenersException();
        }

        requestors.put(requestor, bcsrl);
      }

      // remove a requestor

      void removeRequestor(Object requestor) {
        requestors.remove(requestor);
      }

      // check a requestors listener

      void verifyRequestor(Object requestor, BeanContextServiceRevokedListener bcsrl)
          throws TooManyListenersException {
        BeanContextServiceRevokedListener cbcsrl = (BeanContextServiceRevokedListener) requestors
            .get(requestor);

        if (cbcsrl != null && !cbcsrl.equals(bcsrl)) {
          throw new TooManyListenersException();
        }
      }

      void verifyAndMaybeSetProvider(BeanContextServiceProvider bcsp, boolean isDelegated) {
        BeanContextServiceProvider current;

        if (isDelegated) { // the provider is delegated
          current = delegateProvider;

          if (current == null || bcsp == null) {
            delegateProvider = bcsp;
            return;
          }
        } else { // the provider is registered with this BCS
          current = serviceProvider;

          if (current == null || bcsp == null) {
            serviceProvider = bcsp;
            return;
          }
        }

        if (!current.equals(bcsp)) {
          throw new UnsupportedOperationException(
              "existing service reference obtained from different BeanContextServiceProvider not supported");
        }

      }

      Iterator cloneOfEntries() {
        return ((HashMap) requestors.clone()).entrySet().iterator();
      }

      Iterator entries() {
        return requestors.entrySet().iterator();
      }

      boolean isEmpty() {
        return requestors.isEmpty();
      }

      Class getServiceClass() {
        return serviceClass;
      }

      BeanContextServiceProvider getServiceProvider() {
        return serviceProvider;
      }

      BeanContextServiceProvider getDelegateProvider() {
        return delegateProvider;
      }

      boolean isDelegated() {
        return delegateProvider != null;
      }

      void addRef(boolean delegated) {
        if (delegated) {
          delegateRefs++;
        } else {
          serviceRefs++;
        }
      }


      void releaseRef(boolean delegated) {
        if (delegated) {
          if (--delegateRefs == 0) {
            delegateProvider = null;
          }
        } else {
          if (--serviceRefs <= 0) {
            serviceProvider = null;
          }
        }
      }

      int getRefs() {
        return serviceRefs + delegateRefs;
      }

      int getDelegateRefs() {
        return delegateRefs;
      }

      int getServiceRefs() {
        return serviceRefs;
      }

            /*
             * fields
             */

      Class serviceClass;

      BeanContextServiceProvider serviceProvider;
      int serviceRefs;

      BeanContextServiceProvider delegateProvider; // proxy
      int delegateRefs;

      HashMap requestors = new HashMap(1);
    }

        /*
         * per service reference info ...
         */

    class BCSSCServiceRef {

      BCSSCServiceRef(BCSSCServiceClassRef scref, boolean isDelegated) {
        serviceClassRef = scref;
        delegated = isDelegated;
      }

      void addRef() {
        refCnt++;
      }

      int release() {
        return --refCnt;
      }

      BCSSCServiceClassRef getServiceClassRef() {
        return serviceClassRef;
      }

      boolean isDelegated() {
        return delegated;
      }

            /*
             * fields
             */

      BCSSCServiceClassRef serviceClassRef;
      int refCnt = 1;
      boolean delegated = false;
    }

    BCSSChild(Object bcc, Object peer) {
      super(bcc, peer);
    }

    // note usage of service per requestor, per service

    synchronized void usingService(Object requestor, Object service, Class serviceClass,
        BeanContextServiceProvider bcsp, boolean isDelegated,
        BeanContextServiceRevokedListener bcsrl)
        throws TooManyListenersException, UnsupportedOperationException {

      // first, process mapping from serviceClass to requestor(s)

      BCSSCServiceClassRef serviceClassRef = null;

      if (serviceClasses == null) {
        serviceClasses = new HashMap(1);
      } else {
        serviceClassRef = (BCSSCServiceClassRef) serviceClasses.get(serviceClass);
      }

      if (serviceClassRef == null) { // new service being used ...
        serviceClassRef = new BCSSCServiceClassRef(serviceClass, bcsp, isDelegated);
        serviceClasses.put(serviceClass, serviceClassRef);

      } else { // existing service ...
        serviceClassRef.verifyAndMaybeSetProvider(bcsp, isDelegated); // throws
        serviceClassRef.verifyRequestor(requestor, bcsrl); // throws
      }

      serviceClassRef.addRequestor(requestor, bcsrl);
      serviceClassRef.addRef(isDelegated);

      // now handle mapping from requestor to service(s)

      BCSSCServiceRef serviceRef = null;
      Map services = null;

      if (serviceRequestors == null) {
        serviceRequestors = new HashMap(1);
      } else {
        services = (Map) serviceRequestors.get(requestor);
      }

      if (services == null) {
        services = new HashMap(1);

        serviceRequestors.put(requestor, services);
      } else {
        serviceRef = (BCSSCServiceRef) services.get(service);
      }

      if (serviceRef == null) {
        serviceRef = new BCSSCServiceRef(serviceClassRef, isDelegated);

        services.put(service, serviceRef);
      } else {
        serviceRef.addRef();
      }
    }

    // release a service reference

    synchronized void releaseService(Object requestor, Object service) {
      if (serviceRequestors == null) {
        return;
      }

      Map services = (Map) serviceRequestors.get(requestor);

      if (services == null) {
        return; // oops its not there anymore!
      }

      BCSSCServiceRef serviceRef = (BCSSCServiceRef) services.get(service);

      if (serviceRef == null) {
        return; // oops its not there anymore!
      }

      BCSSCServiceClassRef serviceClassRef = serviceRef.getServiceClassRef();
      boolean isDelegated = serviceRef.isDelegated();
      BeanContextServiceProvider bcsp = isDelegated ? serviceClassRef.getDelegateProvider()
          : serviceClassRef.getServiceProvider();

      bcsp.releaseService(BeanContextServicesSupport.this.getBeanContextServicesPeer(), requestor,
          service);

      serviceClassRef.releaseRef(isDelegated);
      serviceClassRef.removeRequestor(requestor);

      if (serviceRef.release() == 0) {

        services.remove(service);

        if (services.isEmpty()) {
          serviceRequestors.remove(requestor);
          serviceClassRef.removeRequestor(requestor);
        }

        if (serviceRequestors.isEmpty()) {
          serviceRequestors = null;
        }

        if (serviceClassRef.isEmpty()) {
          serviceClasses.remove(serviceClassRef.getServiceClass());
        }

        if (serviceClasses.isEmpty()) {
          serviceClasses = null;
        }
      }
    }

    // revoke a service

    synchronized void revokeService(Class serviceClass, boolean isDelegated, boolean revokeNow) {
      if (serviceClasses == null) {
        return;
      }

      BCSSCServiceClassRef serviceClassRef = (BCSSCServiceClassRef) serviceClasses
          .get(serviceClass);

      if (serviceClassRef == null) {
        return;
      }

      Iterator i = serviceClassRef.cloneOfEntries();

      BeanContextServiceRevokedEvent bcsre = new BeanContextServiceRevokedEvent(
          BeanContextServicesSupport.this.getBeanContextServicesPeer(), serviceClass, revokeNow);
      boolean noMoreRefs = false;

      while (i.hasNext() && serviceRequestors != null) {
        Map.Entry entry = (Map.Entry) i.next();
        BeanContextServiceRevokedListener listener = (BeanContextServiceRevokedListener) entry
            .getValue();

        if (revokeNow) {
          Object requestor = entry.getKey();
          Map services = (Map) serviceRequestors.get(requestor);

          if (services != null) {
            Iterator i1 = services.entrySet().iterator();

            while (i1.hasNext()) {
              Map.Entry tmp = (Map.Entry) i1.next();

              BCSSCServiceRef serviceRef = (BCSSCServiceRef) tmp.getValue();
              if (serviceRef.getServiceClassRef().equals(serviceClassRef)
                  && isDelegated == serviceRef.isDelegated()) {
                i1.remove();
              }
            }

            if (noMoreRefs = services.isEmpty()) {
              serviceRequestors.remove(requestor);
            }
          }

          if (noMoreRefs) {
            serviceClassRef.removeRequestor(requestor);
          }
        }

        listener.serviceRevoked(bcsre);
      }

      if (revokeNow && serviceClasses != null) {
        if (serviceClassRef.isEmpty()) {
          serviceClasses.remove(serviceClass);
        }

        if (serviceClasses.isEmpty()) {
          serviceClasses = null;
        }
      }

      if (serviceRequestors != null && serviceRequestors.isEmpty()) {
        serviceRequestors = null;
      }
    }

    // release all references for this child since it has been unnested.

    void cleanupReferences() {

      if (serviceRequestors == null) {
        return;
      }

      Iterator requestors = serviceRequestors.entrySet().iterator();

      while (requestors.hasNext()) {
        Map.Entry tmp = (Map.Entry) requestors.next();
        Object requestor = tmp.getKey();
        Iterator services = ((Map) tmp.getValue()).entrySet().iterator();

        requestors.remove();

        while (services.hasNext()) {
          Map.Entry entry = (Map.Entry) services.next();
          Object service = entry.getKey();
          BCSSCServiceRef sref = (BCSSCServiceRef) entry.getValue();

          BCSSCServiceClassRef scref = sref.getServiceClassRef();

          BeanContextServiceProvider bcsp =
              sref.isDelegated() ? scref.getDelegateProvider() : scref.getServiceProvider();

          scref.removeRequestor(requestor);
          services.remove();

          while (sref.release() >= 0) {
            bcsp.releaseService(BeanContextServicesSupport.this.getBeanContextServicesPeer(),
                requestor, service);
          }
        }
      }

      serviceRequestors = null;
      serviceClasses = null;
    }

    void revokeAllDelegatedServicesNow() {
      if (serviceClasses == null) {
        return;
      }

      Iterator serviceClassRefs =
          new HashSet(serviceClasses.values()).iterator();

      while (serviceClassRefs.hasNext()) {
        BCSSCServiceClassRef serviceClassRef = (BCSSCServiceClassRef) serviceClassRefs.next();

        if (!serviceClassRef.isDelegated()) {
          continue;
        }

        Iterator i = serviceClassRef.cloneOfEntries();
        BeanContextServiceRevokedEvent bcsre = new BeanContextServiceRevokedEvent(
            BeanContextServicesSupport.this.getBeanContextServicesPeer(),
            serviceClassRef.getServiceClass(), true);
        boolean noMoreRefs = false;

        while (i.hasNext()) {
          Map.Entry entry = (Map.Entry) i.next();
          BeanContextServiceRevokedListener listener = (BeanContextServiceRevokedListener) entry
              .getValue();

          Object requestor = entry.getKey();
          Map services = (Map) serviceRequestors.get(requestor);

          if (services != null) {
            Iterator i1 = services.entrySet().iterator();

            while (i1.hasNext()) {
              Map.Entry tmp = (Map.Entry) i1.next();

              BCSSCServiceRef serviceRef = (BCSSCServiceRef) tmp.getValue();
              if (serviceRef.getServiceClassRef().equals(serviceClassRef) && serviceRef
                  .isDelegated()) {
                i1.remove();
              }
            }

            if (noMoreRefs = services.isEmpty()) {
              serviceRequestors.remove(requestor);
            }
          }

          if (noMoreRefs) {
            serviceClassRef.removeRequestor(requestor);
          }

          listener.serviceRevoked(bcsre);

          if (serviceClassRef.isEmpty()) {
            serviceClasses.remove(serviceClassRef.getServiceClass());
          }
        }
      }

      if (serviceClasses.isEmpty()) {
        serviceClasses = null;
      }

      if (serviceRequestors != null && serviceRequestors.isEmpty()) {
        serviceRequestors = null;
      }
    }

        /*
         * fields
         */

    private transient HashMap serviceClasses;
    private transient HashMap serviceRequestors;
  }

  /**
   * <p>
   * Subclasses can override this method to insert their own subclass
   * of Child without having to override add() or the other Collection
   * methods that add children to the set.
   * </p>
   *
   * @param targetChild the child to create the Child on behalf of
   * @param peer the peer if the targetChild and peer are related by BeanContextProxy
   */

  protected BCSChild createBCSChild(Object targetChild, Object peer) {
    return new BCSSChild(targetChild, peer);
  }

  /************************************************************************/

  /**
   * subclasses may subclass this nested class to add behaviors for
   * each BeanContextServicesProvider.
   */

  protected static class BCSSServiceProvider implements Serializable {

    private static final long serialVersionUID = 861278251667444782L;

    BCSSServiceProvider(Class sc, BeanContextServiceProvider bcsp) {
      super();

      serviceProvider = bcsp;
    }

    /**
     * Returns the service provider.
     *
     * @return the service provider
     */
    protected BeanContextServiceProvider getServiceProvider() {
      return serviceProvider;
    }

    /**
     * The service provider.
     */

    protected BeanContextServiceProvider serviceProvider;
  }

  /**
   * subclasses can override this method to create new subclasses of
   * BCSSServiceProvider without having to override addService() in
   * order to instantiate.
   *
   * @param sc the class
   * @param bcsp the service provider
   * @return a service provider without overriding addService()
   */

  protected BCSSServiceProvider createBCSSServiceProvider(Class sc,
      BeanContextServiceProvider bcsp) {
    return new BCSSServiceProvider(sc, bcsp);
  }

  /************************************************************************/

  /**
   * add a BeanContextServicesListener
   *
   * @throws NullPointerException if the argument is null
   */

  public void addBeanContextServicesListener(BeanContextServicesListener bcsl) {
    if (bcsl == null) {
      throw new NullPointerException("bcsl");
    }

    synchronized (bcsListeners) {
      if (bcsListeners.contains(bcsl)) {
        return;
      } else {
        bcsListeners.add(bcsl);
      }
    }
  }

  /**
   * remove a BeanContextServicesListener
   */

  public void removeBeanContextServicesListener(BeanContextServicesListener bcsl) {
    if (bcsl == null) {
      throw new NullPointerException("bcsl");
    }

    synchronized (bcsListeners) {
      if (!bcsListeners.contains(bcsl)) {
        return;
      } else {
        bcsListeners.remove(bcsl);
      }
    }
  }

  /**
   * add a service
   *
   * @param serviceClass the service class
   * @param bcsp the service provider
   */

  public boolean addService(Class serviceClass, BeanContextServiceProvider bcsp) {
    return addService(serviceClass, bcsp, true);
  }

  /**
   * add a service
   *
   * @param serviceClass the service class
   * @param bcsp the service provider
   * @param fireEvent whether or not an event should be fired
   * @return true if the service was successfully added
   */

  protected boolean addService(Class serviceClass, BeanContextServiceProvider bcsp,
      boolean fireEvent) {

    if (serviceClass == null) {
      throw new NullPointerException("serviceClass");
    }
    if (bcsp == null) {
      throw new NullPointerException("bcsp");
    }

    synchronized (BeanContext.globalHierarchyLock) {
      if (services.containsKey(serviceClass)) {
        return false;
      } else {
        services.put(serviceClass, createBCSSServiceProvider(serviceClass, bcsp));

        if (bcsp instanceof Serializable) {
          serializable++;
        }

        if (!fireEvent) {
          return true;
        }

        BeanContextServiceAvailableEvent bcssae = new BeanContextServiceAvailableEvent(
            getBeanContextServicesPeer(), serviceClass);

        fireServiceAdded(bcssae);

        synchronized (children) {
          Iterator i = children.keySet().iterator();

          while (i.hasNext()) {
            Object c = i.next();

            if (c instanceof BeanContextServices) {
              ((BeanContextServicesListener) c).serviceAvailable(bcssae);
            }
          }
        }

        return true;
      }
    }
  }

  /**
   * remove a service
   *
   * @param serviceClass the service class
   * @param bcsp the service provider
   * @param revokeCurrentServicesNow whether or not to revoke the service
   */

  public void revokeService(Class serviceClass, BeanContextServiceProvider bcsp,
      boolean revokeCurrentServicesNow) {

    if (serviceClass == null) {
      throw new NullPointerException("serviceClass");
    }
    if (bcsp == null) {
      throw new NullPointerException("bcsp");
    }

    synchronized (BeanContext.globalHierarchyLock) {
      if (!services.containsKey(serviceClass)) {
        return;
      }

      BCSSServiceProvider bcsssp = (BCSSServiceProvider) services.get(serviceClass);

      if (!bcsssp.getServiceProvider().equals(bcsp)) {
        throw new IllegalArgumentException("service provider mismatch");
      }

      services.remove(serviceClass);

      if (bcsp instanceof Serializable) {
        serializable--;
      }

      Iterator i = bcsChildren(); // get the BCSChild values.

      while (i.hasNext()) {
        ((BCSSChild) i.next()).revokeService(serviceClass, false, revokeCurrentServicesNow);
      }

      fireServiceRevoked(serviceClass, revokeCurrentServicesNow);
    }
  }

  /**
   * has a service, which may be delegated
   */

  public synchronized boolean hasService(Class serviceClass) {
    if (serviceClass == null) {
      throw new NullPointerException("serviceClass");
    }

    synchronized (BeanContext.globalHierarchyLock) {
      if (services.containsKey(serviceClass)) {
        return true;
      }

      BeanContextServices bcs = null;

      try {
        bcs = (BeanContextServices) getBeanContext();
      } catch (ClassCastException cce) {
        return false;
      }

      return bcs == null ? false : bcs.hasService(serviceClass);
    }
  }

  /************************************************************************/

    /*
     * a nested subclass used to represent a proxy for serviceClasses delegated
     * to an enclosing BeanContext.
     */

  protected class BCSSProxyServiceProvider implements BeanContextServiceProvider,
      BeanContextServiceRevokedListener {

    BCSSProxyServiceProvider(BeanContextServices bcs) {
      super();

      nestingCtxt = bcs;
    }

    public Object getService(BeanContextServices bcs, Object requestor, Class serviceClass,
        Object serviceSelector) {
      Object service = null;

      try {
        service = nestingCtxt.getService(bcs, requestor, serviceClass, serviceSelector, this);
      } catch (TooManyListenersException tmle) {
        return null;
      }

      return service;
    }

    public void releaseService(BeanContextServices bcs, Object requestor, Object service) {
      nestingCtxt.releaseService(bcs, requestor, service);
    }

    public Iterator getCurrentServiceSelectors(BeanContextServices bcs, Class serviceClass) {
      return nestingCtxt.getCurrentServiceSelectors(serviceClass);
    }

    public void serviceRevoked(BeanContextServiceRevokedEvent bcsre) {
      Iterator i = bcsChildren(); // get the BCSChild values.

      while (i.hasNext()) {
        ((BCSSChild) i.next())
            .revokeService(bcsre.getServiceClass(), true, bcsre.isCurrentServiceInvalidNow());
      }
    }

        /*
         * fields
         */

    private BeanContextServices nestingCtxt;
  }

  /************************************************************************/

  /**
   * obtain a service which may be delegated
   */

  public Object getService(BeanContextChild child, Object requestor, Class serviceClass,
      Object serviceSelector, BeanContextServiceRevokedListener bcsrl)
      throws TooManyListenersException {
    if (child == null) {
      throw new NullPointerException("child");
    }
    if (serviceClass == null) {
      throw new NullPointerException("serviceClass");
    }
    if (requestor == null) {
      throw new NullPointerException("requestor");
    }
    if (bcsrl == null) {
      throw new NullPointerException("bcsrl");
    }

    Object service = null;
    BCSSChild bcsc;
    BeanContextServices bcssp = getBeanContextServicesPeer();

    synchronized (BeanContext.globalHierarchyLock) {
      synchronized (children) {
        bcsc = (BCSSChild) children.get(child);
      }

      if (bcsc == null) {
        throw new IllegalArgumentException("not a child of this context"); // not a child ...
      }

      BCSSServiceProvider bcsssp = (BCSSServiceProvider) services.get(serviceClass);

      if (bcsssp != null) {
        BeanContextServiceProvider bcsp = bcsssp.getServiceProvider();
        service = bcsp.getService(bcssp, requestor, serviceClass, serviceSelector);
        if (service != null) { // do bookkeeping ...
          try {
            bcsc.usingService(requestor, service, serviceClass, bcsp, false, bcsrl);
          } catch (TooManyListenersException tmle) {
            bcsp.releaseService(bcssp, requestor, service);
            throw tmle;
          } catch (UnsupportedOperationException uope) {
            bcsp.releaseService(bcssp, requestor, service);
            throw uope; // unchecked rt exception
          }

          return service;
        }
      }

      if (proxy != null) {

        // try to delegate ...

        service = proxy.getService(bcssp, requestor, serviceClass, serviceSelector);

        if (service != null) { // do bookkeeping ...
          try {
            bcsc.usingService(requestor, service, serviceClass, proxy, true, bcsrl);
          } catch (TooManyListenersException tmle) {
            proxy.releaseService(bcssp, requestor, service);
            throw tmle;
          } catch (UnsupportedOperationException uope) {
            proxy.releaseService(bcssp, requestor, service);
            throw uope; // unchecked rt exception
          }

          return service;
        }
      }
    }

    return null;
  }

  /**
   * release a service
   */

  public void releaseService(BeanContextChild child, Object requestor, Object service) {
    if (child == null) {
      throw new NullPointerException("child");
    }
    if (requestor == null) {
      throw new NullPointerException("requestor");
    }
    if (service == null) {
      throw new NullPointerException("service");
    }

    BCSSChild bcsc;

    synchronized (BeanContext.globalHierarchyLock) {
      synchronized (children) {
        bcsc = (BCSSChild) children.get(child);
      }

      if (bcsc != null) {
        bcsc.releaseService(requestor, service);
      } else {
        throw new IllegalArgumentException("child actual is not a child of this BeanContext");
      }
    }
  }

  /**
   * @return an iterator for all the currently registered service classes.
   */

  public Iterator getCurrentServiceClasses() {
    return new BCSIterator(services.keySet().iterator());
  }

  /**
   * @return an iterator for all the currently available service selectors (if any) available for
   * the specified service.
   */

  public Iterator getCurrentServiceSelectors(Class serviceClass) {

    BCSSServiceProvider bcsssp = (BCSSServiceProvider) services.get(serviceClass);

    return bcsssp != null ? new BCSIterator(bcsssp.getServiceProvider()
        .getCurrentServiceSelectors(getBeanContextServicesPeer(), serviceClass)) : null;
  }

  /**
   * BeanContextServicesListener callback, propagates event to all
   * currently registered listeners and BeanContextServices children,
   * if this BeanContextService does not already implement this service
   * itself.
   *
   * subclasses may override or envelope this method to implement their
   * own propagation semantics.
   */

  public void serviceAvailable(BeanContextServiceAvailableEvent bcssae) {
    synchronized (BeanContext.globalHierarchyLock) {
      if (services.containsKey(bcssae.getServiceClass())) {
        return;
      }

      fireServiceAdded(bcssae);

      Iterator i;

      synchronized (children) {
        i = children.keySet().iterator();
      }

      while (i.hasNext()) {
        Object c = i.next();

        if (c instanceof BeanContextServices) {
          ((BeanContextServicesListener) c).serviceAvailable(bcssae);
        }
      }
    }
  }

  /**
   * BeanContextServicesListener callback, propagates event to all
   * currently registered listeners and BeanContextServices children,
   * if this BeanContextService does not already implement this service
   * itself.
   *
   * subclasses may override or envelope this method to implement their
   * own propagation semantics.
   */

  public void serviceRevoked(BeanContextServiceRevokedEvent bcssre) {
    synchronized (BeanContext.globalHierarchyLock) {
      if (services.containsKey(bcssre.getServiceClass())) {
        return;
      }

      fireServiceRevoked(bcssre);

      Iterator i;

      synchronized (children) {
        i = children.keySet().iterator();
      }

      while (i.hasNext()) {
        Object c = i.next();

        if (c instanceof BeanContextServices) {
          ((BeanContextServicesListener) c).serviceRevoked(bcssre);
        }
      }
    }
  }

  /**
   * Gets the <tt>BeanContextServicesListener</tt> (if any) of the specified
   * child.
   *
   * @param child the specified child
   * @return the BeanContextServicesListener (if any) of the specified child
   */
  protected static final BeanContextServicesListener getChildBeanContextServicesListener(
      Object child) {
    try {
      return (BeanContextServicesListener) child;
    } catch (ClassCastException cce) {
      return null;
    }
  }

  /**
   * called from superclass child removal operations after a child
   * has been successfully removed. called with child synchronized.
   *
   * This subclass uses this hook to immediately revoke any services
   * being used by this child if it is a BeanContextChild.
   *
   * subclasses may envelope this method in order to implement their
   * own child removal side-effects.
   */

  protected void childJustRemovedHook(Object child, BCSChild bcsc) {
    BCSSChild bcssc = (BCSSChild) bcsc;

    bcssc.cleanupReferences();
  }

  /**
   * called from setBeanContext to notify a BeanContextChild
   * to release resources obtained from the nesting BeanContext.
   *
   * This method revokes any services obtained from its parent.
   *
   * subclasses may envelope this method to implement their own semantics.
   */

  protected synchronized void releaseBeanContextResources() {
    Object[] bcssc;

    super.releaseBeanContextResources();

    synchronized (children) {
      if (children.isEmpty()) {
        return;
      }

      bcssc = children.values().toArray();
    }

    for (int i = 0; i < bcssc.length; i++) {
      ((BCSSChild) bcssc[i]).revokeAllDelegatedServicesNow();
    }

    proxy = null;
  }

  /**
   * called from setBeanContext to notify a BeanContextChild
   * to allocate resources obtained from the nesting BeanContext.
   *
   * subclasses may envelope this method to implement their own semantics.
   */

  protected synchronized void initializeBeanContextResources() {
    super.initializeBeanContextResources();

    BeanContext nbc = getBeanContext();

    if (nbc == null) {
      return;
    }

    try {
      BeanContextServices bcs = (BeanContextServices) nbc;

      proxy = new BCSSProxyServiceProvider(bcs);
    } catch (ClassCastException cce) {
      // do nothing ...
    }
  }

  /**
   * Fires a <tt>BeanContextServiceEvent</tt> notifying of a new service.
   *
   * @param serviceClass the service class
   */
  protected final void fireServiceAdded(Class serviceClass) {
    BeanContextServiceAvailableEvent bcssae = new BeanContextServiceAvailableEvent(
        getBeanContextServicesPeer(), serviceClass);

    fireServiceAdded(bcssae);
  }

  /**
   * Fires a <tt>BeanContextServiceAvailableEvent</tt> indicating that a new
   * service has become available.
   *
   * @param bcssae the <tt>BeanContextServiceAvailableEvent</tt>
   */
  protected final void fireServiceAdded(BeanContextServiceAvailableEvent bcssae) {
    Object[] copy;

    synchronized (bcsListeners) {
      copy = bcsListeners.toArray();
    }

    for (int i = 0; i < copy.length; i++) {
      ((BeanContextServicesListener) copy[i]).serviceAvailable(bcssae);
    }
  }

  /**
   * Fires a <tt>BeanContextServiceEvent</tt> notifying of a service being revoked.
   *
   * @param bcsre the <tt>BeanContextServiceRevokedEvent</tt>
   */
  protected final void fireServiceRevoked(BeanContextServiceRevokedEvent bcsre) {
    Object[] copy;

    synchronized (bcsListeners) {
      copy = bcsListeners.toArray();
    }

    for (int i = 0; i < copy.length; i++) {
      ((BeanContextServiceRevokedListener) copy[i]).serviceRevoked(bcsre);
    }
  }

  /**
   * Fires a <tt>BeanContextServiceRevokedEvent</tt>
   * indicating that a particular service is
   * no longer available.
   *
   * @param serviceClass the service class
   * @param revokeNow whether or not the event should be revoked now
   */
  protected final void fireServiceRevoked(Class serviceClass, boolean revokeNow) {
    Object[] copy;
    BeanContextServiceRevokedEvent bcsre = new BeanContextServiceRevokedEvent(
        getBeanContextServicesPeer(), serviceClass, revokeNow);

    synchronized (bcsListeners) {
      copy = bcsListeners.toArray();
    }

    for (int i = 0; i < copy.length; i++) {
      ((BeanContextServicesListener) copy[i]).serviceRevoked(bcsre);
    }
  }

  /**
   * called from BeanContextSupport writeObject before it serializes the
   * children ...
   *
   * This class will serialize any Serializable BeanContextServiceProviders
   * herein.
   *
   * subclasses may envelope this method to insert their own serialization
   * processing that has to occur prior to serialization of the children
   */

  protected synchronized void bcsPreSerializationHook(ObjectOutputStream oos) throws IOException {

    oos.writeInt(serializable);

    if (serializable <= 0) {
      return;
    }

    int count = 0;

    Iterator i = services.entrySet().iterator();

    while (i.hasNext() && count < serializable) {
      Map.Entry entry = (Map.Entry) i.next();
      BCSSServiceProvider bcsp = null;

      try {
        bcsp = (BCSSServiceProvider) entry.getValue();
      } catch (ClassCastException cce) {
        continue;
      }

      if (bcsp.getServiceProvider() instanceof Serializable) {
        oos.writeObject(entry.getKey());
        oos.writeObject(bcsp);
        count++;
      }
    }

    if (count != serializable) {
      throw new IOException("wrote different number of service providers than expected");
    }
  }

  /**
   * called from BeanContextSupport readObject before it deserializes the
   * children ...
   *
   * This class will deserialize any Serializable BeanContextServiceProviders
   * serialized earlier thus making them available to the children when they
   * deserialized.
   *
   * subclasses may envelope this method to insert their own serialization
   * processing that has to occur prior to serialization of the children
   */

  protected synchronized void bcsPreDeserializationHook(ObjectInputStream ois)
      throws IOException, ClassNotFoundException {

    serializable = ois.readInt();

    int count = serializable;

    while (count > 0) {
      services.put(ois.readObject(), ois.readObject());
      count--;
    }
  }

  /**
   * serialize the instance
   */

  private synchronized void writeObject(ObjectOutputStream oos) throws IOException {
    oos.defaultWriteObject();

    serialize(oos, (Collection) bcsListeners);
  }

  /**
   * deserialize the instance
   */

  private synchronized void readObject(ObjectInputStream ois)
      throws IOException, ClassNotFoundException {

    ois.defaultReadObject();

    deserialize(ois, (Collection) bcsListeners);
  }


    /*
     * fields
     */

  /**
   * all accesses to the <code> protected transient HashMap services </code>
   * field should be synchronized on that object
   */
  protected transient HashMap services;

  /**
   * The number of instances of a serializable <tt>BeanContextServceProvider</tt>.
   */
  protected transient int serializable = 0;


  /**
   * Delegate for the <tt>BeanContextServiceProvider</tt>.
   */
  protected transient BCSSProxyServiceProvider proxy;


  /**
   * List of <tt>BeanContextServicesListener</tt> objects.
   */
  protected transient ArrayList bcsListeners;
}
